package com.talkweb.osmharmony.views;

import com.talkweb.osmharmony.ResourceTable;
import com.talkweb.osmharmony.util.ResourceHelper;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.render.Texture;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.global.resource.Resource;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.image.common.ImageInfo;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

/**
 * @author Fabrice Fontaine
 * @since 6.1.0
 */
public class CustomZoomButtonsDisplay {

    public enum HorizontalPosition {LEFT, CENTER, RIGHT}

    public enum VerticalPosition {TOP, CENTER, BOTTOM}

    private final MapView mMapView;
    private final Point mUnrotatedPoint = new Point();
    private PixelMap mZoomInBitmapEnabled;
    private PixelMap mZoomOutBitmapEnabled;
    private PixelMap mZoomInBitmapDisabled;
    private PixelMap mZoomOutBitmapDisabled;
    private Paint mAlphaPaint;
    private int mBitmapSize;
    private HorizontalPosition mHorizontalPosition;
    private VerticalPosition mVerticalPosition;
    private boolean mHorizontalOrVertical;
    private float mMargin; // as fraction of the bitmap size
    private float mPadding; // as fraction of the bitmap size
    private float mAdditionalPixelMarginLeft; // additional left margin in pixels
    private float mAdditionalPixelMarginTop; // additional top margin in pixels
    private float mAdditionalPixelMarginRight; // additional right margin in pixels
    private float mAdditionalPixelMarginBottom;    // additional bottom margin in pixels
    private float mPixelMarginLeft; // calculated overall left margin in pixels
    private float mPixelMarginTop; // calculated overall top margin in pixels
    private float mPixelMarginRight; // calculated overall right margin in pixels
    private float mPixelMarginBottom;    // calculated overall bottom margin in pixels

    public CustomZoomButtonsDisplay(final MapView pMapView) {
        mMapView = pMapView;
        // default values
        setPositions(true, HorizontalPosition.CENTER, VerticalPosition.BOTTOM);
        setMarginPadding(.5f, .5f);
    }

    public void setPositions(
            final boolean pHorizontalOrVertical,
            final HorizontalPosition pHorizontalPosition, final VerticalPosition pVerticalPosition) {
        mHorizontalOrVertical = pHorizontalOrVertical;
        mHorizontalPosition = pHorizontalPosition;
        mVerticalPosition = pVerticalPosition;
    }

    /**
     * sets margin and padding as fraction of the bitmap width
     */
    public void setMarginPadding(final float pMargin, final float pPadding) {
        mMargin = pMargin;
        mPadding = pPadding;
        refreshPixelMargins();
    }

    /**
     * sets additional margin in pixels
     *
     * @since 6.1.3
     */
    public void setAdditionalPixelMargins(final float pLeft, final float pTop, final float pRight, final float pBottom) {
        mAdditionalPixelMarginLeft = pLeft;
        mAdditionalPixelMarginTop = pTop;
        mAdditionalPixelMarginRight = pRight;
        mAdditionalPixelMarginBottom = pBottom;
        refreshPixelMargins();
    }

    /**
     * calculate overall margins in pixels
     *
     * @since 6.1.3
     */
    private void refreshPixelMargins() {
        final float bitmapFractionMarginInPixels = mMargin * mBitmapSize;
        mPixelMarginLeft = bitmapFractionMarginInPixels + mAdditionalPixelMarginLeft;
        mPixelMarginTop = bitmapFractionMarginInPixels + mAdditionalPixelMarginTop;
        mPixelMarginRight = bitmapFractionMarginInPixels + mAdditionalPixelMarginRight;
        mPixelMarginBottom = bitmapFractionMarginInPixels + mAdditionalPixelMarginBottom;
    }

    public void setPixelMaps(final PixelMap pInEnabled, final PixelMap pInDisabled,
                             final PixelMap pOutEnabled, final PixelMap pOutDisabled) {
        mZoomInBitmapEnabled = pInEnabled;
        mZoomInBitmapDisabled = pInDisabled;
        mZoomOutBitmapEnabled = pOutEnabled;
        mZoomOutBitmapDisabled = pOutDisabled;
        ImageInfo imageInfo = mZoomInBitmapEnabled.getImageInfo();
        if(imageInfo != null){
            mBitmapSize = imageInfo.size.width;
        }
        refreshPixelMargins();
    }

    protected PixelMap getZoomPixelMap(final boolean pInOrOut, final boolean pEnabled) {
        final PixelMap icon = getIcon(pInOrOut);
        mBitmapSize = icon.getImageInfo().size.width;
        refreshPixelMargins();

        PixelMap.InitializationOptions options = new PixelMap.InitializationOptions();
        options.size = new Size(mBitmapSize, mBitmapSize);
        options.editable = false;
        options.pixelFormat = PixelFormat.ARGB_8888;
        final PixelMap pixelMap = PixelMap.create(options);

        final Canvas canvas = new Canvas(new Texture(pixelMap));
        final Paint backgroundPaint = new Paint();
        backgroundPaint.setColor(pEnabled ? Color.WHITE : Color.LTGRAY);
        backgroundPaint.setStyle(Paint.Style.FILL_STYLE);
        canvas.drawRect(0, 0, mBitmapSize - 1, mBitmapSize - 1, backgroundPaint);
        canvas.drawPixelMapHolder(new PixelMapHolder(icon), 0, 0, new Paint());
        return pixelMap;
    }

    protected PixelMap getIcon(final boolean pInOrOut) {
        final int resourceId = pInOrOut ? ResourceTable.Media_sharp_add_black_36 : ResourceTable.Media_sharp_remove_black_36;
        Resource resource = ResourceHelper.getResource(mMapView.getContext(), resourceId);

        ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
        ImageSource imageSource = ImageSource.create(resource, sourceOptions);

        ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
        decodingOptions.desiredPixelFormat = PixelFormat.ARGB_8888;
        decodingOptions.editable = false;
        decodingOptions.sampleSize = ImageSource.DecodingOptions.DEFAULT_SAMPLE_SIZE;
        return imageSource.createPixelmap(decodingOptions);
    }

    public void draw(final Canvas pCanvas, final float pAlpha01, final boolean pZoomInEnabled, final boolean pZoomOutEnabled) {
        if (pAlpha01 == 0) {
            return;
        }
        final Paint paint;
        if (pAlpha01 == 1) {
            paint = new Paint();
        } else {
            if (mAlphaPaint == null) {
                mAlphaPaint = new Paint();
            }
            mAlphaPaint.setAlpha((int) (pAlpha01 * 255));
            paint = mAlphaPaint;
        }

        pCanvas.drawPixelMapHolder(
                new PixelMapHolder(getPixelMap(true, pZoomInEnabled)),
                getTopLeft(true, true),
                getTopLeft(true, false),
                paint);
        pCanvas.drawPixelMapHolder(
                new PixelMapHolder(getPixelMap(false, pZoomOutEnabled)),
                getTopLeft(false, true),
                getTopLeft(false, false),
                paint);
    }

    private float getTopLeft(final boolean pInOrOut, final boolean pXOrY) {
        final float topLeft;
        if (pXOrY) {
            topLeft = getFirstLeft(mMapView.getWidth());
            if (!mHorizontalOrVertical) { // vertical: same left
                return topLeft;
            }
            if (!pInOrOut) { // horizontal: zoom out first
                return topLeft;
            }
            return topLeft + mBitmapSize + mPadding * mBitmapSize;
        }
        topLeft = getFirstTop(mMapView.getHeight());
        if (mHorizontalOrVertical) { // horizontal: same top
            return topLeft;
        }
        if (pInOrOut) { // vertical: zoom in first
            return topLeft;
        }
        return topLeft + mBitmapSize + mPadding * mBitmapSize;
    }

    private float getFirstLeft(final int pMapViewWidth) {
        switch (mHorizontalPosition) {
            case LEFT:
                return mPixelMarginLeft;
            case RIGHT:
                return pMapViewWidth - mPixelMarginRight - mBitmapSize
                        - (mHorizontalOrVertical ? mPadding * mBitmapSize + mBitmapSize : 0);
            case CENTER:
                return pMapViewWidth / 2f
                        - (mHorizontalOrVertical ? mPadding * mBitmapSize / 2 + mBitmapSize : mBitmapSize / 2f);
        }
        throw new IllegalArgumentException();
    }

    private float getFirstTop(final int pMapViewHeight) {
        switch (mVerticalPosition) {
            case TOP:
                return mPixelMarginTop;
            case BOTTOM:
                return pMapViewHeight - mPixelMarginBottom - mBitmapSize
                        - (mHorizontalOrVertical ? 0 : mPadding * mBitmapSize + mBitmapSize);
            case CENTER:
                return pMapViewHeight / 2f
                        - (mHorizontalOrVertical ? mBitmapSize / 2f : mPadding * mBitmapSize / 2 + mBitmapSize);
        }
        throw new IllegalArgumentException();
    }

    private PixelMap getPixelMap(final boolean pInOrOut, final boolean pEnabled) {
        if (mZoomInBitmapEnabled == null) {
            setPixelMaps(
                    getZoomPixelMap(true, true),
                    getZoomPixelMap(true, false),
                    getZoomPixelMap(false, true),
                    getZoomPixelMap(false, false)
            );
        }
        if (pInOrOut) {
            return pEnabled ? mZoomInBitmapEnabled : mZoomInBitmapDisabled;
        }
        return pEnabled ? mZoomOutBitmapEnabled : mZoomOutBitmapDisabled;
    }

    @Deprecated
    public boolean isTouchedRotated(final TouchEvent pMotionEvent, final boolean pInOrOut) {
        MmiPoint mmiPoint = pMotionEvent.getPointerPosition(pMotionEvent.getIndex());
        if (mMapView.getMapOrientation() == 0) {
            mUnrotatedPoint.modify((int) mmiPoint.getX(), (int) mmiPoint.getY());
        } else {
            mMapView.getProjection().rotateAndScalePoint(
                    (int) mmiPoint.getX(), (int) mmiPoint.getY(), mUnrotatedPoint);
        }
        return isTouched(mUnrotatedPoint.getPointXToInt(), mUnrotatedPoint.getPointYToInt(), pInOrOut);
    }

    /**
     * @since 6.1.3
     */
    public boolean isTouched(final TouchEvent pMotionEvent, final boolean pInOrOut) {
        int action = pMotionEvent.getAction();
        if (action == TouchEvent.PRIMARY_POINT_DOWN || action == TouchEvent.PRIMARY_POINT_UP) {
            MmiPoint mmiPoint = pMotionEvent.getPointerPosition(pMotionEvent.getIndex());
            return isTouched((int) mmiPoint.getX(), (int) mmiPoint.getY(), pInOrOut);
        } else {
            return false;
        }
    }

    private boolean isTouched(final int pEventX, final int pEventY, final boolean pInOrOut) {
        return isTouched(pInOrOut, true, pEventX)
                && isTouched(pInOrOut, false, pEventY);
    }

    private boolean isTouched(final boolean pInOrOut, final boolean pXOrY, final float pEvent) {
        final float topLeft = getTopLeft(pInOrOut, pXOrY);
        return pEvent >= topLeft && pEvent <= topLeft + mBitmapSize;
    }
}
